home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tricks of the Mac Game Programming Gurus
/
TricksOfTheMacGameProgrammingGurus.iso
/
More Source
/
Libraries
/
SAT 2.3b4
/
Demo ƒ
/
OffscreenToys SAT demo ƒ
/
OffscreenToysSAT.p
< prev
next >
Wrap
Text File
|
1995-03-30
|
24KB
|
781 lines
{--------- OFFSCREEN TOYS with SAT ---------}
{by Ingemar Ragnemalm 1994}
{Offscreen Toys is a nice little demo I made to make a SAT-like demo with complete source}
{code, independent of any libraries (except Apple's). Now, this should be easier to do with}
{SAT, right? Well, partially so, but while adapting it to SAT, I ran into a minor flaw that}
{SAT had (and doesn't have from version 2.1 and up), namely that drawing took place after}
{moving sprites, but before checking for collisions, which didn't look too good in programs}
{where sprites bounce off each other. After fixing this flaw, I would say that the SAT}
{version is indeed a bit better than the independent version. The result is quite a bit faster}
{and with asynch sound.}
{}
{As yet another SAT demo, what does it give us?}
{• If you wonder what it costs to have a real event loop, or good collision handling, this demo}
{shows that pretty well.}
{• Demonstrates a moveable window AND fast mode at the same time, with the precautions}
{that demands when moving the window. (The window mustn't be moved outside the screen,}
{some internal SAT variables – that you otherwise should never care about – must be adjusted,}
{and we must stay word-aligned to make it work in b/w).}
{}
{I don't consider this demo final in any way. Known flaws:}
{• I have made some mistakes in the port-setting. Nothing fatal, I think. FIXED.}
{• I don't protect the user from moving the window outside the screen, which might be fatal}
{when the SAT blitters are turned on. FIXED.}
{• Like in the "real" Offscreen Toys, there is a bug that causes sprites to disappear for a short}
{while, since the position gets negative. No big deal.}
{• The code for the marble should be separated from the main program, to make the code easier}
{to follow.}
{I'll fix those things when I find time and inspiration for it - but you are welcome to do it if}
{you want!}
program OffscreenToysSAT;
uses
{$ifc UNDEFINED THINK_PASCAL}
Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, {PackIntf,}
TextEdit, Traps, {Desk,}
Devices, Memory, SegLoad, Scrap, ToolUtils, {OSEvents,}
OSUtils, Menus, Resources, StandardFile, GestaltEqu, Files, Errors,
{$elsec}
InterfacesUI,
{$endc}
SAT_OT; {Customized version}
{ --- PART 1: Variables and constants: -----------------------------------------}
const
kAppleID = 128;
kFileID = 129;
kMBarHeight = 20; { We assume 20 pixels menu bar for window sizing and dragging.}
kWindID = 128; { Window resource ID }
kAboutAlertID = 128; { Alert resource ID }
kSpriteNumber = 5; { Number of moving objects }
var
gColorQDFlag: Boolean; {True if 32-bit QD exists. If not, we run everything in b/w.}
gHasWNE: Boolean; {True if we can use WaitNextEvent}
gSoundFlag: Boolean; {True if we want sound.}
gFast: Boolean;
gWhoa: Boolean; {True when we want to quit}
gCollisionFlag: Boolean; {Collisions or not?}
{Menu handles}
appleMenu, fileMenu: MenuHandle;
{The window we'll be using}
gWind: WindowPtr;
{Our two offscreens:}
{offScreen, backScreen: GrafPtr;}
{A cicn loded as a face}
gCicn: FacePtr;
kgck: Handle;
{Sprite information. In real games, I prefer making a linked list of records, like I do in}
{SAT, and a lot more information for each, but here we want it *simple*.}
{- position: The positions in local coordinates for the window}
{- fixedPos: 16 times position, which gives us fixed-point numbers}
{- speed: Speed vectors that is added to fixedPos for every frame}
{- r: Rectangles used in drawing, for remembering what part of the screen to update}
{position, fixedPos: array[1..kSpriteNumber] of Point;}
{speed: array[1..kSpriteNumber] of Point;}
{r: array[1..kSpriteNumber] of Rect;}
{}
{…but, since this now uses SAT, the linked list is built-in, so all the variables above are in}
{the sprite record!}
{A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
gBowlSize: Longint;
{ --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
{BailOut: Emergency exit. We go here on most errors. Real programs report what the}
{problem is. You may wish to put a breakpoint in BailOut when debugging.}
procedure BailOut;
begin
SysBeep(1); {Minimal error message. Use alert in real programs.}
halt;
end;
{Several functions deleted since SAT handles what they do.}
{ --- PART 3: Application specific routines: ---------------------------------}
{mouse clicks, keydowns, background tasks and update events: This is where all}
{the action is. :-) I include some empty procedures for you to fill in if you want to}
{use this demo as application shell.}
{Mouse click in window content}
procedure DoMouse (where: Point; modifiers: Longint);
begin
end;
{Keydown.}
procedure DoKey (theKey: Char; modifiers: Longint);
begin
end;
const
kWallBounce = 7; {1/10-ths of speed kept after wallbounce}
kBallDiameterSquared = 32 * 32; {Diameter 32, squared}
{A rather boring subroutine that moves the sprites i and j away from each other.}
{I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
{decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
{objects, I'd be happy to put it in.}
{I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
{value as reuseable code - depends on your application.}
procedure Separate (i, j: SpritePtr);
var
initVector, nowVector: Point;
absH, absV: integer;
moveH, moveV: integer;
frac: integer;
{Normal signum function (which I don't think is in the libs)}
function Sgn (x: integer): integer;
begin
if x > 0 then
Sgn := 1
else if x < 0 then
Sgn := -1
else
Sgn := 0;
end;
begin {Separate}
frac := 0;
initVector.h := i^.position.h - j^.position.h;
initVector.v := i^.position.v - j^.position.v;
absH := abs(initVector.h);
absV := abs(initVector.v);
moveH := Sgn(initVector.h);
moveV := Sgn(initVector.v);
if moveH = 0 then
if moveV = 0 then
moveV := 1;
repeat
if absH > absV then
begin
i^.position.h := i^.position.h + moveH;
j^.position.h := j^.position.h - moveH;
frac := frac + absV;
if frac > absH then
begin
i^.position.v := i^.position.v + moveV;
j^.position.v := j^.position.v - moveV;
frac := frac - absH;
end
end
else
begin
i^.position.v := i^.position.v + moveV;
j^.position.v := j^.position.v - moveV;
frac := frac + absH;
if frac > absV then
begin
i^.position.h := i^.position.h + moveH;
j^.position.h := j^.position.h - moveH;
frac := frac - absV;
end
end;
nowVector.h := i^.position.h - j^.position.h;
nowVector.v := i^.position.v - j^.position.v;
until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
i^.fixedPos.h := BSL(i^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
i^.fixedPos.v := BSL(i^.position.v, 4);
j^.fixedPos.h := BSL(j^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
j^.fixedPos.v := BSL(j^.position.v, 4);
end;{Separate}
{Split a vector (v1) into one component parallell to another vector (direction) and one}
{orthogonal to it.}
procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
var
l2, v1pr: Longint;
begin
{parallell := direction * (v1 DOT direction) /|direction|**2}
{normal := v1 - parallell}
l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
parallell.h := direction.h * v1pr div l2;
parallell.v := direction.v * v1pr div l2;
normal.h := v1.h - parallell.h;
normal.v := v1.v - parallell.v;
end; {SplitVector}
procedure HandleMarble (i: SpritePtr);
var
vector: Point;
begin
{Modify fixed-point position by speed}
i^.fixedPos.h := i^.fixedPos.h + i^.speed.h;
i^.fixedPos.v := i^.fixedPos.v + i^.speed.v;
{Make position by shifting away the 4 binary "decimals"}
if i^.fixedPos.h >= 0 then
i^.position.h := BSR(i^.fixedPos.h, 4)
else
i^.position.h := BitOr(BSR(i^.fixedPos.h, 4), $f000);
if i^.fixedPos.v >= 0 then
i^.position.v := BSR(i^.fixedPos.v, 4)
else
i^.position.v := BitOr(BSR(i^.fixedPos.v, 4), $f000);
{Outside the allowed area?}
if i^.position.h < 0 then
begin
i^.speed.h := abs(i^.speed.h) * kWallBounce div 10 + 1;
{ i^.position.h := 0;}
end;
if i^.position.v < 0 then
begin
i^.speed.v := abs(i^.speed.v) * kWallBounce div 10 + 1;
{ i^.position.v := 0; }
end;
if i^.position.h + gCicn^.iconMask.bounds.right > gSAT.offScreen.port^.portRect.right then
begin
i^.speed.h := -abs(i^.speed.h) * kWallBounce div 10 - 1;
{ i^.position.h := gSAT.offScreen^.portRect.right - gCicn^.iconMask.bounds.right; }
end;
if i^.position.v + gCicn^.iconMask.bounds.bottom > gSAT.offScreen.port^.portRect.bottom then
begin
i^.speed.v := -abs(i^.speed.v) * kWallBounce div 10 - 1;
{ i^.position.v := gSAT.offScreen^.portRect.bottom - gCicn^.iconMask.bounds.bottom; }
end;
{Are we in the bowl? If we are, accelerate towards the center.}
vector.h := i^.position.h + 16 - BSR(gSAT.offScreen.port^.portRect.right, 1);
vector.v := i^.position.v + 16 - BSR(gSAT.offScreen.port^.portRect.bottom, 1);
if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
begin
i^.speed.h := i^.speed.h - vector.h div 2;
i^.speed.v := i^.speed.v - vector.v div 2;
end;
end; {position/speed loop}
procedure HitMarble (i, j: SpritePtr);
var
vector: Point;
squaredLength: Longint;
p1, p2, n1, n2, tmpSpeed: Point;
begin
if not gCollisionFlag then
exit(HitMarble);
{Find the vector between them}
vector.h := i^.position.h - j^.position.h;
vector.v := i^.position.v - j^.position.v;
squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
{If it is shorter than the diameter of a ball…}
if squaredLength < kBallDiameterSquared then
begin
{Move them away from each other}
Separate(i, j);
{if false then}
begin
{Swap the speed components that are parallell to "vector" (this allows for "touches", very}
{nice and realistic bounces)}
SplitVector(i^.speed, vector, p1, n1);
SplitVector(j^.speed, vector, p2, n2);
j^.speed.h := p1.h + n2.h;
j^.speed.v := p1.v + n2.v;
i^.speed.h := p2.h + n1.h;
i^.speed.v := p2.v + n1.v;
end;
{Old Offscreen Toys just swapped the speed, as commented out below. This is not as realistic.}
{tmpSpeed := i^.speed;}
{i^.speed := j^.speed;}
{j^.speed := tmpSpeed;}
{Play a sound. SAT is good at playing sounds!}
if gSoundFlag then
SATSoundPlay(kgck, 1, false);
end;
end; {collision loop}
procedure SetupMarble (i: SpritePtr);
begin
i^.fixedPos.h := BSL(i^.position.h, 4);
i^.fixedPos.v := BSL(i^.position.v, 4);
i^.speed.h := Random mod 32;
i^.speed.v := Random mod 32;
i^.task := @HandleMarble;
i^.hitTask := @HitMarble;
i^.face := gCicn;
SetRect(i^.hotRect, 0, 0, 32, 32);
end;
{A static variable only used in DrawBackground}
var
thePat: PixPatHandle;
procedure DrawBackground;
const
patID = 128;
var
r: Rect;
mycolorFlag: Boolean;
{A little routine for setting the forecolor with a single line.}
procedure OTForeColor (red, green, blue: integer);
var
theColor: RGBColor;
begin
theColor.red := red;
theColor.green := green;
theColor.blue := blue;
RGBForeColor(theColor);
end;
var
savePort: SATPort;
begin {DrawBackground}
SATGetPort(savePort);
SATSetPortBackScreen;
{Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
{For drawing the background, let's make a local flag that tells us if we shold draw}
{b/w patterns or color ones.}
if gColorQDFlag then
mycolorFlag := gSAT.initDepth > 1
else
mycolorFlag := false;
if mycolorFlag then
begin
if thePat = nil then
thePat := GetPixPat(patID);
PenPixPat(thePat)
end
else
begin
if thePat = nil then
thePat := PixPatHandle(GetResource('ppat', patID));
PenPat(thePat^^.pat1Data);
end;
PaintRect(gSAT.backScreen.port^.portRect);
PenNormal;
{Then we draw some circles.}
r := gSAT.backScreen.port^.portRect;
InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4; {Tells how big the "bowl" is!}
if mycolorFlag then
begin
OTForeColor(-10000, -10000, -10000);
PaintOval(r);
end
else
{$IFC UNDEFINED THINK_PASCAL}
FillOval(r, qd.ltGray);
{$ELSEC}
FillOval(r, ltGray);
{$ENDC}
InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
if mycolorFlag then
begin
OTForeColor(-25000, -25000, -25000);
PaintOval(r);
end
else
{$IFC UNDEFINED THINK_PASCAL}
FillOval(r, qd.gray);
{$ELSEC}
FillOval(r, gray);
{$ENDC}
InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
if mycolorFlag then
begin
OTForeColor(20000, 20000, 20000);
PaintOval(r);
end
else
{$IFC UNDEFINED THINK_PASCAL}
FillOval(r, qd.dkGray);
{$ELSEC}
FillOval(r, dkGray);
{$ENDC}
InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
if gSAT.colorFlag then
OTForeColor(0, 0, 0);
PaintOval(r);
{ InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);}
{ if mycolorFlag then}
{ begin}
{ OTForeColor(0, 0, 0);}
{ PaintOval(r);}
{ end}
{ else}
{ FillOval(r, black); }
SATSetPortOffScreen;
CopyBits(gSAT.backScreen.port^.portBits, gSAT.offScreen.port^.portBits, gSAT.backScreen.port^.portRect, gSAT.backScreen.port^.portRect, srcCopy, nil);
SATSetPort(savePort);
end; {DrawBackground}
{DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
{Note: If you are making a really Mac-friendly program, this is where you should drive}
{the animation. However, it is hard to get high framerate then, since other programs}
{(the Finder included) will process events which will make it less smooth.}
procedure DoBackground;
var
tmpRect: Rect;
i, j: integer;
vector: Point;
saveGD: GDHandle;
savePort: GrafPtr;
tmpSpeed: Point;
begin {DoBackground}
SATRun(gFast); {Eller konfigurerbart?}
end; {DoBackground}
{DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
{Note to beginners: A program without update events processing is not a real Mac program!}
{All drawing you do must reach the update event handler in some way, or it might be lost,}
{or worse, partially erased, which is really ugly.}
procedure DoUpdate;
var
saveGD: GDHandle;
savePort: GrafPtr;
begin
if SATDepthChangeTest then
DrawBackground;
BeginUpdate(gWind);
SATRedraw;
EndUpdate(gWind);
end;
{DoAppleMenu and DoFileMenu: handle menu selections}
procedure DoAppleMenu (item: integer);
var
str: Str255;
h: Handle;
savePort: SATPort;
ignore: integer;
begin
if item = 1 then
begin
if Alert(kAboutAlertID, nil) = 1 then
; {Ignore result}
end
else
{Apple menu other than "About": Code from TransSkel}
begin
SATGetPort(savePort);
GetMenuItemText(appleMenu, item, str); {Old intf: GetItem}
SetResLoad(false);
h := GetNamedResource('DRVR', str);
SetResLoad(true);
if h <> nil then
begin
ReserveMem(GetResourceSizeOnDisk(h) + $1000); {Old intf: ResrvMem, SizeResource}
ignore := OpenDeskAcc(str);
end;
SATSetPort(savePort);
end;
end; {DoAppleMenu}
procedure DoFileMenu (item: integer);
var
start, finish, frames: Longint;
fpsStr: Str255;
begin
case item of
1:
{Run animation without event processing until the user clicks the mouse}
{Note: This runs the animation at maximum speed. In real programs, we}
{must limit the speed with the system clock, e.g. inspect TickCount.}
begin
start := TickCount;
frames := 0;
while not Button do
begin
DoBackground;
frames := frames + 1;
end;
finish := TickCount;
NumToString(frames * 60 div (finish - start), fpsStr);
SATReportStr(stringof(fpsStr, ' frames/second'));
{ParamText(fpsStr, ' frames/second', '', '');}
{if Alert(129, nil) = 1 then}
end;
2:
begin
gCollisionFlag := not gCollisionFlag;
CheckItem(fileMenu, 2, gCollisionFlag);
end;
3:
begin
gFast := not gFast;
CheckItem(fileMenu, 3, gFast);
end;
4:
begin
gSoundFlag := not gSoundFlag;
CheckItem(fileMenu, 4, gSoundFlag);
end;
{Set the flag that tells the program to quit.}
6:
gWhoa := true;
end; {case}
end; {DoFileMenu}
{ --- PART 4: Event processing: -----------------------------------------}
{MenuSelection: Menu selection by mouse or command-key:}
procedure MenuSelection (whatSelection: longInt);
begin
case HiWord(whatSelection) of
kAppleID:
DoAppleMenu(LoWord(whatSelection));
kFileID:
DoFileMenu(LoWord(whatSelection));
end; {case}
HiLiteMenu(0);
end;
{MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
{using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
procedure MainLoop;
const
kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
var
hasEvent: Boolean;
theEvent: EventRecord;
theKey: Char;
whatSelection: Longint;
whichPart: integer;
whichWindow: WindowPtr;
r: rect;
p: Point;
begin
{Get the next event. Use WaitNextEvent if possible.}
if gHasWNE then
hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
else
begin
SystemTask;
hasEvent := GetNextEvent(everyEvent, theEvent);
end;
{OK, so what happened then?}
if hasEvent then
case theEvent.what of
mouseDown:
begin
whichPart := FindWindow(theEvent.where, whichWindow);
case whichPart of
inMenuBar:
begin
whatSelection := MenuSelect(theEvent.where);
MenuSelection(whatSelection);
end;
inSysWindow:
SystemClick(theEvent, whichWindow);
inGoAway:
if (TrackGoAway(whichWindow, theEvent.where)) then
gWhoa := true;
inDrag:
begin
if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
SelectWindow(whichWindow);
r := gSAT.bounds; {How big is the screen?}
{Was: screenBits.bounds;{How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
r.top := r.top + kMBarHeight; { Skip down past menu bar }
InsetRect(r, 4, 4);
{ LIMIT THE DRAGGING so no part of the window can get outside the screen! Cut down on r}
{depending on where the click is? This is necessary if we use "fast mode", that is if we do}
{SATRun(true) or any other operation with custom blitters.}
SetPort(whichWindow);
p := theEvent.where;
GlobalToLocal(p);
r.bottom := r.bottom - (whichWindow^.portRect.bottom - p.v);
r.left := r.left + p.h;
r.right := r.right - (whichWindow^.portRect.right - p.h);
DragWindow(whichWindow, theEvent.where, r);
if whichWindow = gSAT.wind.port then
SATWindMoved;
end;
inGrow:
; {Ignored - we don't resize}
inContent:
if (whichWindow <> FrontWindow) then
SelectWindow(whichWindow)
else
DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
end; {case whichPart}
end; {mouseDown}
keyDown, autoKey:
begin
theKey := char(BitAnd(theEvent.message, charCodeMask));
if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
MenuSelection(MenuKey(theKey))
else
DoKey(theKey, theEvent.modifiers);
end;
updateEvt:
{There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
if WindowPtr(theEvent.message) = gWind then
DoUpdate;
{Handle disk inserts like TransSkel.}
diskEvt:
if (HiWord(theEvent.message) <> noErr) then
begin
DILoad;
if DIBadMount(Point($00400040), theEvent.message) = 0 then
;
DIUnload;
end; {diskEvt}
otherwise {Other events are ignored}
end; {case}
DoBackground;
end;
{ --- PART 5: Initializations: -----------------------------------------}
{OTInit: Initialize global flags, menus and window}
procedure OTInit;
const
{Trap numbers}
_WaitNextEvent = $A860;
_GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
k32bQD = $AB1D;
_SndPlay = $A805;
begin
{In case this isn't Think Pascal we have to make the standard inits ourselves.}
{$IFC UNDEFINED THINK_PASCAL}
SATInitToolbox;
{$ENDC}
gHasWNE := SATTrapAvailable(_WaitNextEvent);
gColorQDFlag := SATTrapAvailable(k32bQD) and SATTrapAvailable(_GetCIcon); {???}
gWhoa := false;
gCollisionFlag := false;
{gSoundFlag := TrapAvailable(_SndPlay); – Let SAT decide if we CAN or not!}
gSoundFlag := true;
{What more should I check for? Check with Gestalt instead?}
{$IFC UNDEFINED THINK_PASCAL}
qd.randSeed := TickCount; {Seed the random number generator - TickCount is good enough.}
{$ELSEC}
randSeed := TickCount; {Seed the random number generator - TickCount is good enough.}
{$ENDC}
{Get the window, a color window if we are going to use color.}
if gColorQDFlag then
gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
else
gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
{Some menus. We could read these from resources.}
appleMenu := NewMenu(kAppleID, stringof(char($14)));
AppendMenu(appleMenu, 'About Offscreen Toys SAT…;(-');
AppendResMenu(appleMenu, 'DRVR'); {Old intf: AddResMenu}
InsertMenu(appleMenu, 0); { put apple menu at end of menu bar }
fileMenu := NewMenu(kFileID, 'File');
AppendMenu(fileMenu, 'Try max speed;Collisions;Use SAT blitters;Use sound;(-;Quit/Q');
InsertMenu(fileMenu, 0); { put file menu at end of menu bar }
DrawMenuBar;
CheckItem(fileMenu, 4, gSoundFlag);
end;
{OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
procedure OTOffscreensInit;
var
savePort: SATPort;
r: Rect;
i: integer;
sp: SpritePtr;
begin {OTOffscreensInit}
SATGetPort(savePort);
SATConfigure(false, kNoSort, kForwardOneCollision, 32); {Only call *one* hitTask, not both.}
{r := gWind^.portRect;}
{OffsetRect(r, -r.left, -r.top);}
SATCustomInit(0, 0, gWind^.portRect, gWind, nil, false, false, false, true, false);
DrawBackground;
{Done drawing!}
{For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
{background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
SATSetPort(savePort);
{Get the cicn resource}
{Note: You can, of course, use several cicns and switch between.}
gCicn := SATGetFace(128);
kgck := SATGetNamedSound('Kgck');
{Initialize the sprites:}
for i := 1 to kSpriteNumber do
sp := SATNewSprite(1, SATRand(gSAT.offScreen.port^.portRect.right - 32), (i - 1) * (gSAT.offScreen.port^.portRect.bottom - 32) div 5 + SATRand((gSAT.offScreen.port^.portRect.bottom - 32) div 5), @SetupMarble);
if SATSoundInitChannels(2) < 2 then
SysBeep(1);
end;
{ --- MAIN PROGRAM BODY: -----------------------------------------}
begin
OTInit; {General initializations}
OTOffscreensInit; {Set up the offscreen grafports}
InitCursor; {Set the cursor to arrow in case it isn't.}
SATSetPortScreen; {The front window is a good port to use.}
{Run until quit or click in the close box.}
repeat
SATSetPortScreen; {The front window is a good port to use.}
MainLoop;
until gWhoa;
{No cleanup is necessary here.}
{We could DisposeGWorld, but that isn't necessary when we are quitting.}
SATSoundShutup;
end.